<?php

declare (strict_types=1);

namespace think\admin\service;

use think\admin\Exception;
use think\admin\extend\HttpExtend;
use think\admin\Service;

/**
 * 萤石开放平台接口
 * @class CameraService
 * @package think\admin\service
 *
 */
class CameraService extends Service
{

    private string $appKey;
    private string $appSecret;

    private string $url;

    /**
     * 获取播放地址
     * @param string $deviceSerial 直播源，例如427734222，均采用英文符号，限制50个
     * @param string|null $startTime ezopen协议地址的本地录像/云存储录像回放开始时间,示例：2019-12-01 00:00:00
     * @param string|null $stopTime ezopen协议地址的本地录像/云存储录像回放开始时间,示例：2019-12-01 00:00:00
     * @param int $channelNo 通道号,，非必选，默认为1
     * @param int $protocol 流播放协议，1-ezopen、2-hls、3-rtmp、4-flv，默认为1
     * @param int $quality 视频清晰度，1-高清（主码流）、2-流畅（子码流）
     * @param int $type ezopen协议地址的类型，1-预览，2-本地录像回放，3-云存储录像回放，非必选，默认为1
     * @return array
     */
    public function getLiveAddress(
        string $deviceSerial,
        ?string $startTime = '',
        ?string $stopTime = '',
        int $channelNo = 1,
        int $protocol = 1,
        int $quality = 1,
        int $type = 1
    ): array {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/v2/live/address/get';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => $deviceSerial,
            'channelNo' => $channelNo,
            'protocol' => $protocol,
            'quality' => $quality,
            'type' => $type,
            'startTime' => $startTime,
            'stopTime' => $stopTime
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, [$result['data']['url'], $accessToken], '请求成功'];
        } else {
            return [false, '请求失败'];
        }
    }


    /**
     * 获取AccessToken
     * @param string|null $appKey
     * @param string|null $appSecret
     * @return string
     */
    public function getAccessToken(?string $appKey, ?string $appSecret): string
    {
        $accessToken = '';
        $file = @file_get_contents('runtime/log/ys.log');
        if ($file) {
            $data = json_decode($file, true);
            if ($data['expireTime'] < time() * 1000) {
                unlink('runtime/log/ys.log');
            } else {
                $accessToken = $data['accessToken'];
            }
        }
        if ($accessToken !== '') {
            return $accessToken;
        }

        $appKey = $appKey ?? $this->appKey;
        $appSecret = $appSecret ?? $this->appSecret;
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query(['appKey' => $appKey, 'appSecret' => $appSecret]);
        $uri = 'lapp/token/get';
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            @file_put_contents(syspath("runtime/log/ys.log"), json_encode($result['data']));
            return $result['data']['accessToken'];
        } else {
            return $accessToken;
        }
    }

    /**
     * 添加设备
     * @param string $deviceSerial 设备序列号,存在英文字母的设备序列号，字母需为大写
     * @param string $validateCode 设备验证码，设备机身上的六位大写字母
     * @return array
     */
    public function addDevice(string $deviceSerial, string $validateCode): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/add';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => strtoupper($deviceSerial),
            'validateCode' => strtoupper($validateCode)
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, '操作成功'];
        } else {
            return [false, $result['msg']];
        }
    }


    /**
     * 删除设备
     * @param string $deviceSerial 设备序列号,存在英文字母的设备序列号，字母需为大写
     * @return array
     */
    public function deleteDevice(string $deviceSerial): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/delete';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => strtoupper($deviceSerial)
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, '删除成功'];
        } else {
            return [false, '操作失败'];
        }
    }


    /**
     * 获取单个设备信息
     * @param string $deviceSerial
     * @return array
     * "data": {
     * "deviceSerial": "427734168",
     * "deviceName": "",
     * "model": "",
     * "status": 1,
     * "defence": 0,
     * "isEncrypt": 1,
     * "alarmSoundMode": 2,
     * "offlineNotify": 1,
     * "category": "C5Si",
     * "netType": "wire",
     * "signal": "100%"
     * },
     */
    public function infoDevice(string $deviceSerial): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/info';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => strtoupper($deviceSerial)
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, $result['data'], '获取单个设备成功'];
        } else {
            return [false, [], '操作失败'];
        }
    }

    /**
     * 修改设备名称
     * @param string $deviceSerial 设备序列号,存在英文字母的设备序列号，字母需为大写
     * @param string $deviceName 设备名称，长度不大于50字节，不能包含特殊字符
     * @return array
     */
    public function updateDevice(string $deviceSerial, string $deviceName): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/name/update';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => strtoupper($deviceSerial),
            'deviceName' => $deviceName
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, '名称修改成功'];
        } else {
            return [false, '操作失败'];
        }
    }

    /**
     * 设备抓拍图片
     * @param string $deviceSerial 设备序列号,存在英文字母的设备序列号，字母需为大写
     * @param int $channelNo 通道号，IPC设备填写1
     * @return array
     */
    public function captureDevice(string $deviceSerial, int $channelNo = 1): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/capture';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => strtoupper($deviceSerial),
            'channelNo' => $channelNo
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, $result['data']['picUrl'], '抓拍成功'];
        } else {
            return [false, '操作失败'];
        }
    }


    /**
     * 获取设备列表
     * @param int $pageStart 分页起始页，从0开始
     * @param int $pageSize 分页大小，默认为10，最大为50
     * @return array
     *  deviceSerial    String    设备序列号,存在英文字母的设备序列号，字母需为大写
     *  deviceName    String    设备名称
     *  deviceType    String    设备类型
     *  status    int    在线状态：0-不在线，1-在线
     *  defence    int    具有防护能力的设备布撤防状态：0-睡眠，8-在家，16-外出，普通IPC布撤防状态：0-撤防，1-布防
     *  deviceVersion    int    设备版本号
     */
    public function listDevice(int $pageStart, int $pageSize): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/list';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'pageStart' => $pageStart,
            'pageSize' => $pageSize
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, $result['data'], '列表获取成功'];
        } else {
            return [false, '操作失败'];
        }
    }


    /**
     * 获取摄像头列表
     * @param int $pageStart
     * @param int $pageSize
     * @return array
     *  deviceSerial    String    设备序列号
     * channelNo    int    通道号
     * channelName    String    通道名
     * status    int    在线状态：0-不在线，1-在线（该字段已废弃）
     * picUrl    String    图片地址（大图），若在萤石客户端设置封面则返回封面图片，未设置则返回默认图片
     * isEncrypt    int    是否加密，0：不加密，1：加密
     * videoLevel    int    视频质量：0-流畅，1-均衡，2-高清，3-超清
     * permission    int    分享设备的权限字段
     */
    public function listCamera(int $pageStart, int $pageSize): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/camera/list';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'pageStart' => $pageStart,
            'pageSize' => $pageSize
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, $result['data'], '列表获取成功'];
        } else {
            return [false, '操作失败'];
        }
    }


    /**
     * 设备状态获取
     * @param string $deviceSerial 设备序列号,存在英文字母的设备序列号，字母需为大写
     * @param int $channelNo 通道号，IPC设备填写1
     * @return array
     *  privacyStatus    int    隐私状态: 0：隐私状态关闭；1：隐私状态打开；-1：初始值；2：不支持,C1专用,-2:设备没有上报或者设备不支持该状态
     * pirStatus    int    红外状态，1：红外启用，0：红外禁用，-1：初始值，2：不支持,-2:设备没有上报或者设备不支持该状态
     * alarmSoundMode    int    告警声音模式，0：短叫，1：长叫，2：静音,3:自定义语音,-1:设备没有上报或者设备不支持该状态
     * battryStatus    int    电池电量,1到100(%)，-1:设备没有上报或者设备不支持该状态
     * lockSignal    int    门锁和网关间的无线信号，百分比表示 差值超过10上报,-1:设备没有上报或者设备不支持该状态
     * diskNum    int    挂载的sd硬盘数量,-1:设备没有上报或者设备不支持该状态
     * diskState    String    sd硬盘状态:0:正常;1:存储介质错;2:未格式化;3:正在格式化;返回形式:一个硬盘表示为"0---------------",两个硬盘表示为"00--------------",以此类推;-1:设备没有上报或者设备不支持该状态
     * cloudStatus    int    云存储状态: -2:设备不支持;-1: 未开通;0: 未激活;1: 激活;2: 过期
     * nvrDiskNum    int    NVR上挂载的硬盘数量: -1:设备没有上报或者设备不支持;-2:未关联,类似于NVR类型的上级设备
     * nvrDiskState    String    NVR上挂载的硬盘状态:0:正常;1:存储介质错;2:未格式化;3:正在格式化;返回形式:一个硬盘表示为"0---------------",两个硬盘表示为"00--------------",以此类推;-1:设备没有上报或者设备不支持该状态;-2:未关联,类似于NVR类型的上级设备
     */
    public function getStatus(string $deviceSerial, int $channelNo): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/status/get';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => strtoupper($deviceSerial),
            'channelNo' => $channelNo
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, $result['data'], '设备状态获取成功'];
        } else {
            return [false, '操作失败'];
        }
    }


    /**
     * 关闭加密
     * @param string $deviceSerial
     * @return array
     */
    public function encryptOff(string $deviceSerial): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/encrypt/off';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => strtoupper($deviceSerial)
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, '加密关闭成功'];
        } else {
            return [false, '操作失败'];
        }
    }


    /**
     * 开启加密
     * @param string $deviceSerial
     * @return array
     */
    public function encryptOn(string $deviceSerial): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/encrypt/on';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => strtoupper($deviceSerial)
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, '加密开启成功'];
        } else {
            return [false, '操作失败'];
        }
    }


    /**
     * 云台控制
     * @param string $deviceSerial
     * @param int $channelNo
     * @param int $direction
     * @param int $speed
     * @return array
     */
    public function ptzStart(string $deviceSerial, int $channelNo = 1, int $direction = 0, int $speed = 1): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/ptz/start';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => strtoupper($deviceSerial),
            'channelNo' => $channelNo,
            'direction' => $direction,
            'speed' => $speed
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, $result['msg']];
        } else {
            return [false, $result['msg']];
        }
    }

    /**
     * 停止云台控制
     * @param string $deviceSerial
     * @param int $channelNo
     * @param int $direction
     * @return array
     */
    public function ptzStop(string $deviceSerial, int $channelNo = 1, int $direction = 0): array
    {
        $accessToken = $this->getAccessToken($this->appKey, $this->appSecret);
        $uri = 'lapp/device/ptz/stop';
        $options = ['headers' => ['Content-Type: application/x-www-form-urlencoded']];
        $data = http_build_query([
            'accessToken' => $accessToken,
            'deviceSerial' => strtoupper($deviceSerial),
            'channelNo' => $channelNo,
            'direction' => $direction
        ]);
        $result = json_decode(HttpExtend::post($this->url . $uri, $data, $options), true);
        if (intval($result['code']) === 200) {
            return [true, $result['msg']];
        } else {
            return [false, $result['msg']];
        }
    }


    /**
     * @return $this
     * @throws Exception
     */
    protected function initialize(): CameraService
    {
        if (!$this->appKey) {
            $this->appKey = sysconfig('OTHER', 'SQM_CAMERA_YS_APPKEY');
        }
        if (!$this->appSecret) {
            $this->appSecret = sysconfig('OTHER', 'SQM_CAMERA_YS_APPSECRET');
        }
        if (!$this->url) {
            $this->url = 'https://open.ys7.com/api/';
        }
        return $this;
    }
}
